﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;

namespace ColladaViewer
{
    using Point = System.Drawing.Point;
    using Microsoft.Xna.Framework.Input;
    using Omi.Xna.Collada.Importer;
    using Omi.Xna.Collada.Model;
    using Omi.Xna.Collada.Importer.Processing;
    using System.IO;
    using Omi.Xna.Collada.Model.Animation;    

    public class ModelViewControl : GraphicsDeviceControl
    {
        private BoundingBoxDrawer bbDrawer;
        private LineDrawer lineDrawer;
        private ContentManager content;
        private RuntimeLoader loader;
        private IModel model = null;

        private Matrix view;
        private Matrix projection;

        private Vector3 rotation = new Vector3(0, 0, 0);
        private Quaternion rotationQuat = Quaternion.Identity;

        private Vector3 translation = new Vector3(0, 0, 0);
        private Vector3 addedTranslation = Vector3.Zero;
        private float zoom = 1;

        private Point prevMousePos = Point.Empty;

        private bool wireframeMode = false;
        private bool showBoundingBox = false;

        public ProcessingOptions ProcessingOptions = new ProcessingOptions();
        public bool ShowSkeleton { get; set; }

        public Color BackgroundColor = Color.CornflowerBlue;

        private StaticModel sphereModel;
        private Texture2D rainbowTexture;

        private RasterizerState RasterizerState = new RasterizerState()
        {
            FillMode = FillMode.WireFrame,
            CullMode = CullMode.None
        };

        private BlendState BlendState = BlendState.Opaque;

        #region Öffentliche Eigenschaften        

        public bool WireframeMode 
        {
            get
            {
                return wireframeMode;
            }
            set
            {
                if (value != wireframeMode)
                {
                    wireframeMode = value;
                    if (wireframeMode)
                    {                        
                        GraphicsDevice.RasterizerState = new RasterizerState()
                        {
                            FillMode = FillMode.WireFrame,
                            CullMode = CullMode.None
                        };
                    }
                    else
                    {
                        GraphicsDevice.RasterizerState = new RasterizerState()
                        {
                            FillMode = FillMode.Solid,
                            CullMode = CullMode.None
                        };
                    }
                }
            }
        }

        public bool Ready { get; protected set; }

        public bool ShowBoundingBox
        {
            get { return showBoundingBox; }
            set { showBoundingBox = value; }
        }

        public int NumPolygons
        {
            get
            {
                if (model != null) return model.Data.PolyCount;
                else return 0;
            }
        }

        public int NumMeshes
        {
            get
            {
                if (model != null) return model.Data.Meshes.Count;
                else return 0;
            }
        }

        public int NumMaterials
        {
            get
            {
                if (model != null) return model.Data.Effects.Count;
                else return 0;
            }
        }

        public IModel Model { get { return model; } }

        #endregion

        public ModelViewControl()
        {
            Ready = false;
            ShowSkeleton = false;
        }

        void LoadModels()
        {            
            RuntimeLoader loader = new RuntimeLoader(GraphicsDevice);
            ModelData modelData = loader.Load("path/to/model.dae");
        }

        protected override void Initialize()
        {
            content = new ContentManager(Services, "Content");
            loader = new RuntimeLoader(GraphicsDevice, content);

            bbDrawer = new BoundingBoxDrawer(GraphicsDevice);   
            lineDrawer = new LineDrawer(GraphicsDevice);

            RuntimeLoader modelLoader = new RuntimeLoader(GraphicsDevice, null);
            ModelData data = modelLoader.Load("Data/sphere-tri.dae",
                new ProcessingOptions {
                    ModelScale = 0.5f});
            sphereModel = new StaticModel(data);

            using (FileStream fin = new FileStream("Data/rainbow.png", FileMode.Open, FileAccess.Read))
            {
                rainbowTexture = Texture2D.FromStream(GraphicsDevice, fin);
            }            

            CalculateViewProjection();

            this.Resize += ModelViewControl_Resize;
            this.MouseMove += ModelViewControl_MouseMove;
            this.MouseDown += ModelViewControl_MouseDown;
            this.MouseUp += ModelViewControl_MouseUp;

            Ready = true;
            ShowSkeleton = false;
        }

        private void ModelViewControl_MouseDown(object sender, MouseEventArgs e)
        {
            prevMousePos = e.Location;
            Cursor.Hide();

            this.Focus();
        }

        private void ModelViewControl_MouseUp(object sender, MouseEventArgs e)
        {
            Cursor.Show();
        }

        private void ModelViewControl_MouseMove(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                if (e.Location != prevMousePos)
                {
                    Point d = new Point();
                    d.X = e.X - prevMousePos.X;
                    d.Y = e.Y - prevMousePos.Y;

                    float yaw = 0.01f * d.X;
                    float pitch = -0.01f * d.Y;

                    KeyboardState kb = Keyboard.GetState();
                    if (kb.IsKeyDown(Keys.LeftShift)) pitch = 0;
                    if (kb.IsKeyDown(Keys.LeftControl)) yaw = 0;

                    Quaternion rotation = Quaternion.CreateFromYawPitchRoll(yaw, pitch, 0);
                    rotationQuat = Quaternion.Concatenate(rotationQuat, rotation);
                    Invalidate();
                }
            }
            else if (e.Button == MouseButtons.Right)
            {
                if (e.Location != prevMousePos)
                {
                    Point d = new Point();
                    d.X = e.X - prevMousePos.X;
                    d.Y = e.Y - prevMousePos.Y;

                    if (d.X != 0)
                    {
                        MoveX(-0.05f * d.X);
                        Invalidate();
                    }

                    if (d.Y != 0)
                    {
                        MoveY(-0.05f * d.Y);
                        Invalidate();
                    }                    
                }
            }

            prevMousePos = e.Location;
        }

        private void ModelViewControl_Resize(object sender, EventArgs e)
        {
            CalculateViewProjection();
            Invalidate();
        }

        public void CalculateViewProjection()
        {
            float aspectRatio = (float)GraphicsDevice.Viewport.Width /
                (float)GraphicsDevice.Viewport.Height;

            projection = Matrix.CreatePerspectiveFieldOfView(
                MathHelper.PiOver4, aspectRatio, 1.0f, 10000.0f);

            view = Matrix.CreateLookAt(new Vector3(0, 0, -10),
                Vector3.Zero, Vector3.Up);
        }

        public void LoadModel(string filename)
        {
            ModelData data = null;

            try
            {
                data = loader.Load(filename, ProcessingOptions);
            }
            catch (Exception e)
            {
                MessageBox.Show("Fehler beim Laden des Modells: " + e.Message);
                data = null;
                return;
            }

            if (data.JointAnimations.Any())
            {
                model = new SoftwareSkinnedModel(data);                
            }
            else
            {
                model = new StaticModel(data);
            }

            Matrix rot = Matrix.CreateRotationX(-MathHelper.PiOver2);
            model.Data.Rotate(rot);

            AutoScaleModel();
            RealignModel();
            ResetScene();
        }

        private void ResetScene()
        {
            addedTranslation = Vector3.Zero;
            rotation = Vector3.Zero;
            rotationQuat = Quaternion.Identity;
        }

        void AutoScaleModel()
        {
            BoundingSphere sphere = BoundingSphere.CreateFromBoundingBox(model.Data.Bounds);            

            const float preferredSize = 5;            
            float originalSize = sphere.Radius * 2;
            float scalingFactor = preferredSize / originalSize;

            zoom = scalingFactor;
        }

        void RealignModel()
        {
            if (model == null) return;

            // Vertically align model in the middle (according to bounding box)
            BoundingBox bounds = model.Data.Bounds;
            //bounds.Min *= zoom;
            //bounds.Max *= zoom;

            Vector3 center = Vector3.Lerp(bounds.Min, bounds.Max, 0.5f);            

            translation = -center;            
        }

        public void Zoom(float amount)
        {
            if (amount > 0)
            {
                zoom *= 1.05f;
            }
            else
            {
                zoom *= 0.95f;
            }
            
            RealignModel();
        }

        public void RotateX(float amount)
        {
            rotation.X += amount;
        }

        public void RotateY(float amount)
        {
            rotation.Y += amount;
        }

        public void RotateZ(float amount)
        {
            rotation.Z += amount;
        }

        public void MoveY(float amount)
        {
            addedTranslation.Y += amount / zoom;
        }

        public void MoveX(float amount)
        {
            addedTranslation.X += amount / zoom;
        }

        /// <summary>
        /// Disposes the control, unloading the ContentManager.
        /// </summary>
        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                content.Unload();
            }

            base.Dispose(disposing);
        }

        protected override void Draw()
        {
            GraphicsDevice.Clear(BackgroundColor);
            CalculateViewProjection();
            if (model == null) return;

            Matrix world =                      
                Matrix.CreateFromQuaternion(rotationQuat) *
                Matrix.CreateTranslation(translation+addedTranslation) *
                Matrix.CreateScale(zoom);

            if (showBoundingBox)
            {
                bbDrawer.Draw(model.Data.Bounds, world, view, projection);
            }  

            if (ShowSkeleton)
            {                    
                DrawSkeleton(world);

                if (!wireframeMode)
                {
                    // XNA 3.1
                    //GraphicsDevice.RenderState.AlphaBlendEnable = true;
                    //GraphicsDevice.RenderState.SourceBlend = Blend.SourceAlpha;
                    //GraphicsDevice.RenderState.DestinationBlend = Blend.BlendFactor;
                    //GraphicsDevice.RenderState.BlendFactor = new Color(170, 170, 170);

                    // TODO: XNA 4.0?
                }
            }
            else
            {     
                // XNA 3.1
                //GraphicsDevice.RenderState.AlphaBlendEnable = false;

                // TODO: XNA 4.0 ?
            }

            model.Draw(world, view, projection);
        }

        void DrawSkeleton(Matrix world)
        {
            if (model == null)
                return;

            if (model is SoftwareSkinnedModel)
            {
                SoftwareSkinnedModel m = model as SoftwareSkinnedModel;                

                foreach (Joint joint in m.AnimatedJoints)
                {
                    Matrix transform = joint.AbsoluteTransform *
                                       model.Data.RootJoint.Transform * world;                    

                    // Draw joint itself                    
                    sphereModel.Draw(transform, view, projection);

                    if (joint.Parent != null)
                    {
                        // Draw Bone             
                        Vector3 from = Vector3.Transform(Vector3.Zero, transform);
                        Vector3 to = Vector3.Transform(Vector3.Zero,
                                                       joint.Parent.AbsoluteTransform *
                                                       model.Data.RootJoint.Transform * 
                                                       world);

                        lineDrawer.DrawLine(from, to, Matrix.Identity, view, projection);
                    }
                }
            }
        }

        #region Bounding Box Drawer

        class LineDrawer
        {
            private VertexDeclaration _vertexDeclaration;
            private VertexPositionColor[] _vertices;
            private BasicEffect _effect;
            private GraphicsDevice _graphicsDevice;            

            public LineDrawer(GraphicsDevice device)
            {
                _effect = new BasicEffect(device);
                _effect.VertexColorEnabled = true;

                _vertices = new VertexPositionColor[2];
                _vertices[0] = new VertexPositionColor(Vector3.Zero, 
                    Color.Red);
                _vertices[1] = new VertexPositionColor(Vector3.Zero,
                    Color.Red);

                _vertexDeclaration = VertexPositionColor.VertexDeclaration;
                _graphicsDevice = device;
            }

            public void DrawLine(Vector3 from, Vector3 to, Color color,
                                 Matrix world, Matrix view, Matrix projection)
            {
                _vertices[0].Position = from;
                _vertices[1].Position = to;
                _vertices[0].Color = color;
                _vertices[1].Color = color;

                _effect.World = world;
                _effect.View = view;
                _effect.Projection = projection;

                EffectPass pass = _effect.CurrentTechnique.Passes[0];

                pass.Apply();         
                _graphicsDevice.DrawUserPrimitives<VertexPositionColor>(PrimitiveType.LineList, _vertices,
                    0, 1);                                      
            }

            public void DrawLine(Vector3 from, Vector3 to, Matrix world,
                                 Matrix view, Matrix projection)
            {
                DrawLine(from, to, Color.Red, world, view, projection);
            }
        }

        class BoundingBoxDrawer
        {
            private BasicEffect _basicEffect;
            private VertexPositionColor[] _vertices;
            private VertexDeclaration _vertexDeclaration;
            private GraphicsDevice _graphicsDevice;

            public BoundingBoxDrawer(GraphicsDevice device)
            {
                _basicEffect = new BasicEffect(device);                
                _vertices = new VertexPositionColor[24];

                for (int i = 0; i < _vertices.Length; i++)
                {
                    _vertices[i].Color = Color.Yellow;
                }

                _vertexDeclaration = VertexPositionColor.VertexDeclaration;
                _graphicsDevice = device;
            }            

            public void Draw(BoundingBox box, Matrix world, Matrix view, Matrix projection)
            {                
                Vector3 min = box.Min;
                Vector3 max = box.Max;

                _vertices[0].Position = new Vector3(min.X, min.Y, min.Z);
                _vertices[1].Position = new Vector3(max.X, min.Y, min.Z);
                _vertices[2].Position = new Vector3(min.X, min.Y, max.Z);
                _vertices[3].Position = new Vector3(max.X, min.Y, max.Z);
                _vertices[4].Position = new Vector3(min.X, min.Y, min.Z);
                _vertices[5].Position = new Vector3(min.X, min.Y, max.Z);
                _vertices[6].Position = new Vector3(max.X, min.Y, min.Z);
                _vertices[7].Position = new Vector3(max.X, min.Y, max.Z);
                _vertices[8].Position = new Vector3(min.X, max.Y, min.Z);
                _vertices[9].Position = new Vector3(max.X, max.Y, min.Z);
                _vertices[10].Position = new Vector3(min.X, max.Y, max.Z);
                _vertices[11].Position = new Vector3(max.X, max.Y, max.Z);
                _vertices[12].Position = new Vector3(min.X, max.Y, min.Z);
                _vertices[13].Position = new Vector3(min.X, max.Y, max.Z);
                _vertices[14].Position = new Vector3(max.X, max.Y, min.Z);
                _vertices[15].Position = new Vector3(max.X, max.Y, max.Z);
                _vertices[16].Position = new Vector3(min.X, min.Y, min.Z);
                _vertices[17].Position = new Vector3(min.X, max.Y, min.Z);
                _vertices[18].Position = new Vector3(max.X, min.Y, min.Z);
                _vertices[19].Position = new Vector3(max.X, max.Y, min.Z);
                _vertices[20].Position = new Vector3(min.X, min.Y, max.Z);
                _vertices[21].Position = new Vector3(min.X, max.Y, max.Z);
                _vertices[22].Position = new Vector3(max.X, min.Y, max.Z);
                _vertices[23].Position = new Vector3(max.X, max.Y, max.Z);
                
                _basicEffect.World = world;                
                _basicEffect.View = view;                
                _basicEffect.Projection = projection;                
                _basicEffect.VertexColorEnabled = true;
                _basicEffect.LightingEnabled = false;
                _basicEffect.TextureEnabled = false;                

                EffectPass pass = _basicEffect.CurrentTechnique.Passes[0];

                pass.Apply();                
                _graphicsDevice.DrawUserPrimitives<VertexPositionColor>(PrimitiveType.LineList, _vertices, 0, 12);                
            }
        }

        #endregion
    }
}
